﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;



namespace Fortuitus.Core
{
    public class MaskedVectorView : IVectorView
    {
        public bool IsValid { get; private set; }
        private IVector _baseVector;
        private int[] _mask;
        private Dictionary<string, MetricCacheValue> _metricStore;
        private Dictionary<string, object> _metaDataStore;

        public MaskedVectorView(IVector vector, IOrderedList<int> indxMask)
            : this(vector, indxMask.ToArray())
        {

        }

        public MaskedVectorView(IVector vector, int[] indxMask)
        {
            _metaDataStore = new Dictionary<string, object>();
            IsValid = false;
            _baseVector = vector;
            _metricStore = new Dictionary<string, MetricCacheValue>();
            _mask = indxMask.ToArray();
        }

        void OnBaseVectorChanged(object sender, EventArgs e)
        {
            IsValid = true;
        }


        /// <summary>
        /// The length of the vector.
        /// </summary>
        public int Length
        {
            get { return _mask.Length; }
        }

        public double this[int indx]
        {
            get
            {
                if (!IsValid)
                {
                    return _baseVector[_mask[indx]];
                }
                else
                {
                    throw new InvalidOperationException("The underlaying vector has changed, which invalidates this view.");
                }
            }
        }

        /// <summary>
        /// Checks whether a certain metadata key exists.
        /// </summary>
        /// <param name="metaDataKey">The metadata key to check</param>
        /// <returns>A value indicatin whether the metadata exists.</returns>
        public bool HasValue(string metaDataKey)
        {
            bool ret = _metaDataStore.ContainsKey(metaDataKey);
            return ret;
        }

        /// <summary>
        /// Access metadata value with a specific key. 
        /// </summary>
        /// <param name="metaDataKey">The metadata key to access the stored value.</param>
        /// <returns>The metadata value.</returns>
        public object GetValue(string metaDataKey)
        {
            if (_metaDataStore.ContainsKey(metaDataKey))
                return _metaDataStore[metaDataKey];
            else
                throw new ArgumentException("Unknown metadata key.");

        }

        /// <summary>
        /// Sets a metadata value with a specific key.
        /// </summary>
        /// <param name="metaDataKey">The key that's used to store the metadata.</param>
        /// <param name="value">The value to store in the metadata store.</param>
        public void SetValue(string metaDataKey, object value)
        {
            if (_metaDataStore.ContainsKey(metaDataKey))
                _metaDataStore[metaDataKey] = value;
            else
                _metaDataStore.Add(metaDataKey, value);
        }

        /// <summary>
        /// Generic version to access metadata value with a specific key. 
        /// </summary>
        /// <typeparam name="T">The type of the value expected. If the stored value of a different type then an exception will be thrown.</typeparam>
        /// <param name="metaDataKey">The key to access the metadata.</param>
        /// <returns>The stored value.</returns>
        public T GetValue<T>(string metaDataKey)
        {
            object val = GetValue(metaDataKey);
            if (val is T)
                return (T)val;
            else
                throw new ArgumentException("The stored metadata is of different type then expected.");
        }

        /// <summary>
        /// Sets a metadata value with a specific key.
        /// </summary>
        /// <param name="metaDataKey">The key that's used to store the metadata.</param>
        /// <param name="value">The value to store in the metadata store.</param>
        public void SetValue<T>(string metaDataKey, T value)
        {
            this.SetValue(metaDataKey, (object)value);
        }

        /// <summary>
        /// Gets a metric of the this vector.
        /// </summary>
        /// <param name="metricKey">The name of the metric.</param>
        /// <returns>The value of the metric.</returns>
        public double GetMetric(string metricKey)
        {
            if (!IsValid)
            {
                if (_metricStore.ContainsKey(metricKey))
                {
                    if (_metricStore[metricKey].IsValid == false)
                    {
                        _metricStore[metricKey].Value = _metricStore[metricKey].Function(this);
                        _metricStore[metricKey].IsValid = true;
                    }
                    return _metricStore[metricKey].Value;
                }
                else
                    throw new ArgumentException("Unknown metric key.");
            }
            else
            {
                throw new InvalidOperationException("The underlaying vector has changed, which invalidates this view.");
            }

        }

        /// <summary>
        /// Sets a metric with a specific function.
        /// </summary>
        /// <param name="metricKey">The access key for the metric.</param>
        /// <param name="metricFunction">The function to calculate the metric.</param>
        /// <param name="lazyCalculation">An value that indicates whether the metric shoulds be calulated immediately or if needed.</param>
        public void SetMetric(string metricKey, Func<IVector, double> metricFunction, bool calclulateOnInset = false)
        {
            if (_metricStore.ContainsKey(metricKey))
                _metricStore[metricKey] = new MetricCacheValue() { Function = metricFunction, IsValid = false };
            else
                _metricStore.Add(metricKey, new MetricCacheValue() { Function = metricFunction, IsValid = false });

            if (calclulateOnInset)
            {
                _metricStore[metricKey].Value = _metricStore[metricKey].Function(this);
                _metricStore[metricKey].IsValid = true;
            }
        }

        /// <summary>
        /// Returns a generic enumerator that iterates through the IVector.
        /// </summary>
        /// <returns>A IEnumerator<double> for the Vector.</returns>
        public IEnumerator<double> GetEnumerator()
        {

            for (int i = 0; i < _mask.Length; i++)
            {
                yield return this[i];
            }
        }

        /// <summary>
        /// Returns an enumerator that iterates through the IVector.
        /// </summary>
        /// <returns>A IEnumerator for the Vector.</returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            for (int i = 0; i < _mask.Length; i++)
            {
                yield return this[i];
            }
        }


        public event EventHandler Changed;




        public IVector BaseVector
        {
            get { return _baseVector; }
        }


    }


}
